#include <Environment.h>
#include <Hal/Manager.h>
#include <Hal/PhysicalMemoryManager.h>
#include <Hal/PmBitmap.h>
#include <Multiboot2.h>

namespace Hal
{
	uint64_t PhysicalMemoryManager::TotalPages;
	uint64_t PhysicalMemoryManager::FreePages;
	PmBitmap PhysicalMemoryManager::bmp;
	uintptr_t PhysicalMemoryManager::NextFreeApproximation;

	void PhysicalMemoryManager::Init(uintptr_t bitmap)
	{
		TotalPages = FreePages = Environment::TotalMemory() / 0x1000;
		NextFreeApproximation = 0x1000000;

		bmp.Init(bitmap, TotalPages, false);
		bmp.PmReset();

		ParseMMapsHigh();
	}

	void PhysicalMemoryManager::ParseMMapsHigh()
	{
		const Multiboot2::Header &mb = Hal::Manager::GetMb2Header();

		for (size_t i = 0; i < mb.GetMemoryMapsCount(); i++)
		{
			const Multiboot2::MemoryMap &map = mb.GetMemoryMap(i);
			
			if (map.GetBase() < Environment::TotalMemory() && map.IsOver16() && !map.IsAvailable())
			{
				uintptr_t base = map.GetBase();
				size_t pages = map.GetLength() / 0x1000;
				
				bmp.Protect(base, pages);
			}
		}
	}

	uintptr_t PhysicalMemoryManager::Allocate(uintptr_t base)
	{
		/* LockGuard<Spinlock> l(PmmLock); */

		/* Can allocate only on page boundary */
		base &= 0xFFFFF000;

		/* For addresses under 0x1000000 use AllocateBelow16 or AllocateBelow1 if base < 0x10000 */
		if (base < 0x1000000)
			base = 0x1000000;

		/* Check that the approximation is correct. Adjust it if needed */
		CheckApproximation();

		if (base <= NextFreeApproximation)
		{
			/* Great. Fast response... */
			NextFreeApproximation += 0x1000;
			FreePages--;
			bmp.SetPage(NextFreeApproximation - 0x1000);
			return NextFreeApproximation - 0x1000;
		}

		/* Base is over the approximated free frame... need an expensive research */
		uintptr_t ret = bmp.FirstFree(base);
		bmp.SetPage(ret);
		
		return ret;
	}

	void PhysicalMemoryManager::CheckApproximation()
	{
		/* Check for consistency of the approximation */
		if (!NextFreeApproximation || bmp.TestPage(NextFreeApproximation))
			NextFreeApproximation = bmp.FirstFree();
	}
}
